/// Text Overlay Application: Main Class (partial UpdateThread)
/// This class contains the entry point of the application and all the 
/// functions requiered to update local content with the information 
/// received from the ShowControl system

using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;
using System.Xml;
using System.IO;
using System.Threading;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using System.Data.SqlClient;
using System.Data;

namespace TextOverlay
{
    public partial class TextOverlay
    {
        Queue UpdateStringList = new Queue(50, 3.0f);
        private int LastUpdateProcessed = 0;
        private System.Object lock_UpdateData = new System.Object();
        private System.Object lock_ReceiveData = new System.Object();
        private bool PrintUpdates = false;
        /// <summary>
        /// CallBack function for the DataServer.
        /// This function adds all incoming strings to the UpdateStringList array where they
        /// await for processing. The function ProcessOneUpdate will be processing each of them
        /// inside its own thread.
        /// </summary>
        /// <param name="arriveTime">String's arrive time</param>
        /// <param name="xmlMessage">string that contains the xml data</param>
        public void ReceiveData(DateTime arriveTime, string xmlMessage)
        {
            lock (lock_ReceiveData)
            {
                UpdateStringList.Enqueue(CleanXmlString(xmlMessage));
                if (PrintUpdates == true)
                    PrintXmlUpdate(xmlMessage, "XmlUpdate " + arriveTime.Minute.ToString() + arriveTime.Second.ToString() + arriveTime.Millisecond.ToString());
            }
        }

        private StringBuilder XmlBuilder;
        /// <summary>
        /// This function cleans an XML String from invalid xml characters
        /// </summary>
        /// <param name="incoming">The target string</param>
        /// <returns>The target string minus any invalid xml characters</returns>
        public string CleanXmlString(string incoming)
        {
            XmlBuilder = new StringBuilder(incoming.Length);
            char[] str = incoming.ToCharArray();
            for (int x = 0; x < str.Length; x++)
            {
                if(str[x] < 0x20 || str[x] > 0xFFFD)
                {
                    if (str[x] == 0x19) XmlBuilder.Append("&");
                }
                else
                {
                    XmlBuilder.Append(str[x]);
                }
            }
            str = null;
            return XmlBuilder.ToString();
        }

        private string updateElection(string feed)
        {
            string returnFeed = "";

            DataSet ds = new DataSet();
            OpenConnection(ref ds, "Select [Name], LastValue from DataTag", "DATATAG");

            feed = feed.Replace("]", "]|");
            string[] upList = feed.Split('|');

            foreach (string node in upList)
            {
                if (node.Contains("[el"))
                {
                    DataRow[] ddr = ds.Tables[0].Select("[Name]='" + node.ToUpper() + "'");
                    returnFeed += ddr[0]["LastValue"].ToString();
                }
                else
                {
                    returnFeed += node;
                }
            }

            return returnFeed;
            //return feed;
        }

        private void OpenConnection(ref DataSet ds, string command, string tableName)
        {
            SqlConnection myConnection = new SqlConnection("Data Source=SQL01;Initial Catalog=SuperSign;Persist Security Info=True;User ID=sa; Password=sa");
            SqlCommand myCommand = new SqlCommand();

            myCommand.Connection = myConnection;
            myCommand.CommandText = command;

            try
            {
                myConnection.Open();
                SqlDataAdapter ad_data = new SqlDataAdapter(myCommand);
                ad_data.Fill(ds, tableName);
            }
            catch (Exception err)
            {
            }
            finally
            {
                myConnection.Close();
            }
        }

        string xmlMessage;
        string xmlFilt;
        /// <summary>
        /// Pulls the first available update string from the UpdateStringList 
        /// and sends it as an argument to the UpdateData() function for processing.
        /// </summary>
        public void ProcessOneUpdate()
        {
            testCounter++;

            if (testCounter > 100 && isTimeSampling == true)
            {
                Watch3.Tic();
                DateTime Now = Watch2.GetRealTime();
                UDP.Send(LocalHost + " -> " + ((Now.Second * 1000) + Now.Millisecond).ToString());
                testCounter = 0;
                double tic = Watch3.Tic();
                if (tic > 5) Track.LogInfo("Time Monitor: sending UDP Time Sample took " + tic.ToString());
            }

            if (UpdateStringList.Count > 0)
            {
                lock (lock_ReceiveData)
                {
                    xmlMessage = (string)UpdateStringList.Dequeue();
                }
                //if (xmlMessage.Contains("[el"))
                //{
                //    xmlFilt = updateElection(xmlMessage);
                //}
                //else
                    xmlFilt = xmlMessage;

                UpdateData(xmlFilt);
                return;
            }

            //StreamReader re = File.OpenText("D:\\local.xml");
            //xmlMessage = re.ReadToEnd();
            //re.Close();


            //if (xmlMessage != null && xmlMessage.Contains("[el"))
            //{
            //    xmlFilt = updateElection(xmlMessage);

            //    UpdateData(xmlFilt);

            //    Thread.Sleep(15000);
            //}
            //UpdateData("D:\\local.xml");
        }

        /// <summary>
        ///  This function calls all update events in the application, maximum one per frame
        ///  these events are Swaping Scheudle, Load Fonts, Reload Fonts and Load Images.
        /// </summary>
        public void UpdateApplication()
        {
            // Check For ComboCollections awaiting for Swap on any channel
            SwapSchedule();           
            CicleCount++;
            isHolding = false;
            isOverride = false;

            if (FontQueue.Count > DXFontLib.Count)
            {
                LoadNextFont();
                return;
            }

            if (ImageQueue.Count > 0)
            {
                LoadNextImage();
                return;
            }
                       
            if (CicleCount > 36000)
            {
                ReloadFont();
                return;
            }

            return;
        }

       

        private DateTime LastUpdateError;
        private bool ImagesReady = false;
        XmlDataDocument XmlDoc = new XmlDataDocument();
        /// <summary>
        /// Process XML Data received from the Show Control System.
        /// This function identifies if the XML stream received corresponds to a timeline update,
        /// configuration update or to a imagetags update, calling the respective function for each case.
        /// This function is called by the ProcessOneUpdate() function everytime theres a new update in queue
        /// ready to be processed.
        /// </summary>
        /// <param name="XMLStream">string that contains the xml stream</param>
        public void UpdateData(string XMLStream)
        {
            int laststep = 3000;

            //if (XMLStream.Contains("[el"))
                //updateElection(XMLStream);

            try
            {
                lock (lock_UpdateData)
                {               
                    laststep = 3001;
                    bool isLocal = false;
                    XmlDoc = new XmlDataDocument();
                    if(localtest == false && fullmode == true)
                    //if(false)
                    {
                        try 
                        {
                            if (XMLStream.StartsWith("<") && localtest == false)
                                XmlDoc.LoadXml(XMLStream);
                            else
                                XmlDoc.LoadXml("<" + XMLStream);

                        }
                        catch (Exception ex)
                        {
                            Track.LogInfo("Error Loading Xml Update (Invalid XML Character)");
                            Track.LogInfo("*** Attempting to Clean XML Stram");
                            try
                            {
                                XmlDoc.LoadXml(CleanXmlString(XMLStream));
                                Track.LogInfo("*** Succesful");
                            }
                            catch
                            {
                                Track.LogInfo("*** Failed");
                                Track.LogInfo(ex.Message);
                                TimeSpan LastReported = DateTime.Now - LastUpdateError;
                                if (LastReported.Hours > 1)
                                {
                                    LastUpdateError = DateTime.Now;
                                    ReportToSCS("<Activity ID=\"20020\" Desc=\"Error Loading Timeline Update (Bad Format)\" />");
                                }
                            }
                            return;
                        }
                        isLocal = false;
                    }
                    else
                    {
                        if (File.Exists(XMLStream))
                        {
                            //XmlDoc.Load(XMLStream); // TODO: Try and Catch for tricky characters in the xml
                            //XmlDoc.LoadXml(updateElection("<Channel ChannelID=\"3\" ChannelName=\"Tickers\" StartTime=\"00:00:00/00\" EndTime=\"00:30:00/00\" UpdateID=\"0\"><Combo ComboID=\"1\" ComboName=\"Test\" TimelineTime=\"00:00:00/00\" RealTime=\"12:00:00/00\" Duration=\"00:30:00/00\" UpdatedTime=\"12:00:00/00\"><TextString TextStringIndex=\"1\"><String>[loop][caps][el_01_st][el_01_obw][el_01_ob][el_01_mcw][el_01_mc][el_02_st][el_02_obw][el_02_ob][el_02_mcw][el_02_mc][el_03_st][el_03_obw][el_03_ob][el_03_mcw][el_03_mc][el_04_st][el_04_obw][el_04_ob][el_04_mcw][el_04_mc][el_05_st][el_05_obw][el_05_ob][el_05_mcw][el_05_mc][el_06_st][el_06_obw][el_06_ob][el_06_mcw][el_06_mc][el_07_st][el_07_obw][el_07_ob][el_07_mcw][el_07_mc][el_08_st][el_08_obw][el_08_ob][el_08_mcw][el_08_mc][el_09_st][el_09_obw][el_09_ob][el_09_mcw][el_09_mc][el_10_st][el_10_obw][el_10_ob][el_10_mcw][el_10_mc][el_11_st][el_11_obw][el_11_ob][el_11_mcw][el_11_mc][el_12_st][el_12_obw][el_12_ob][el_12_mcw][el_12_mc][el_13_st][el_13_obw][el_13_ob][el_13_mcw][el_13_mc][el_14_st][el_14_obw][el_14_ob][el_14_mcw][el_14_mc][el_15_st][el_15_obw][el_15_ob][el_15_mcw][el_15_mc][b]</String><Details>StartX=\"0\", StartY=\"74\", EndX=\"-70000\", EndY=\"74\", StartFrame=\"0\", EndFrame=\"18000\",  FontSize=\"80\", LineWidth=\"\", LineHeight=\"\", ShowInDisplayAreas=\"\",  Font=\"\"</Details></TextString><TextString TextStringIndex=\"2\"><String>[loop][caps][el_01_gst][el_01_c1w][el_01_c1p][el_01_c1][el_01_c2w][el_01_c2p][el_01_c2][el_01_c3w][el_01_c3p][el_01_c3][el_02_gst][el_02_c1w][el_02_c1p][el_02_c1][el_02_c2w][el_02_c2p][el_02_c2][el_02_c3w][el_02_c3p][el_02_c3][el_03_gst][el_03_c1w][el_03_c1p][el_03_c1][el_03_c2w][el_03_c2p][el_03_c2][el_03_c3w][el_03_c3p][el_03_c3][el_04_gst][el_04_c1w][el_04_c1p][el_04_c1][el_04_c2w][el_04_c2p][el_04_c2][el_04_c3w][el_04_c3p][el_04_c3][el_05_gst][el_05_c1w][el_05_c1p][el_05_c1][el_05_c2w][el_05_c2p][el_05_c2][el_05_c3w][el_05_c3p][el_05_c3][el_06_gst][el_06_c1w][el_06_c1p][el_06_c1][el_06_c2w][el_06_c2p][el_06_c2][el_06_c3w][el_06_c3p][el_06_c3][el_07_gst][el_07_c1w][el_07_c1p][el_07_c1][el_07_c2w][el_07_c2p][el_07_c2][el_07_c3w][el_07_c3p][el_07_c3][el_08_gst][el_08_c1w][el_08_c1p][el_08_c1][el_08_c2w][el_08_c2p][el_08_c2][el_08_c3w][el_08_c3p][el_08_c3][b]</String><Details>StartX=\"0\", StartY=\"186\", EndX=\"-90000\", EndY=\"186\", StartFrame=\"0\", EndFrame=\"18000\",  FontSize=\"80\", LineWidth=\"\", LineHeight=\"\", ShowInDisplayAreas=\"\",  Font=\"\"</Details></TextString></Combo></Channel>"));

                            StreamReader re = File.OpenText(XMLStream);
                            string input = re.ReadToEnd();
                            re.Close();
                            //input = updateElection(input);
                            XmlDoc.LoadXml(input);
                            isLocal = true;   
                        }
                        else
                        {
                            if (XMLStream == "" || XMLStream =="<ImageTags></ImageTags>")
                            {
                                Track.LogInfo("File not Found: " + XMLStream);
                                ImagesReady = true;
                                return;
                            }
                            else
                            {
                                XmlDoc.LoadXml(XMLStream);
                            }
                        }
                    }

                    laststep = 3002;
                    XmlNode xmlChannel = XmlDoc.SelectSingleNode("//Channel");
                    XmlNode xmlDisplay = XmlDoc.SelectSingleNode("//TEXTDISPLAY");
                    XmlNode xmlImage = XmlDoc.SelectSingleNode("//ImageTags");

                    laststep = 3003;
                    // Check if the Xml Stram is a Timeline Update
                    if (xmlChannel != null && ImagesReady == true)
                    {
                        laststep = 3004;
                        int UpdateId = Convert.ToInt32(xmlChannel.Attributes["UpdateID"].InnerText);
                        string ChannelName = xmlChannel.Attributes["ChannelName"].InnerText;
                        if (ChannelName != null)
                        {
                            LastUpdateProcessed = UpdateId;
                        }

                        laststep = 3005;
                        if (UpdateTimeline(XmlDoc, true) == true)
                        {
                            Track.LogInfo("Update Timeline: " + xmlChannel.Attributes["UpdateID"].InnerText + " (" + xmlChannel.Attributes["StartTime"].InnerText + " -> " + xmlChannel.Attributes["EndTime"].InnerText + ")");
                        }
                        return;
                    }

                    laststep = 3006;
                    if (xmlDisplay != null)
                    {
                        UpdateConfig(XmlDoc, isLocal);
                        return;
                    }

                    laststep = 3007;
                    if (xmlImage != null)
                    {
                        LoadImagesToQueue(XMLStream);
                        ImagesReady = true;
                        return;
                    }

                    if (ImagesReady == false)
                        Track.LogInfo("Ignoring Update (Images not Ready)");
                    return;
                }
            }
            catch
            {
                Track.LogInfo("Error in UpdateData() function (Code " + laststep.ToString() + ")");
            }
        }

        /// <summary>
        /// Updates a Channel's Timeline.
        /// This function updates a fragment of timeline with the new information contained 
        /// in an incoming XML stream. The update only affects the region specified on the xml update
        /// conserving all the information of the timeline outside that region.
        /// </summary>
        /// <param name="xmlDocument">The incoming XML stream containing the new Timeline information</param>
        /// <param name="Local">Bool that indicates if the update has been loaded from a local storage unit or if it was received from the show control system</param>
        /// <returns></returns>
        public bool UpdateTimeline(XmlDataDocument xmlDocument, bool Local)
        {
            // make sure that we have created the timelines before
            // timelines are created by the configuration updates
            if (Timeline == null)
                return false;

            // Find the corresponding Timeline using the ChannelID information
            int targetChannel = -1;
            XmlNode xmlChannel = xmlDocument.SelectSingleNode("//Channel");
            for (int x = 0; x < Timeline.Length; x++)
            {
                int TimelineUpdateID = Convert.ToInt32(xmlChannel.Attributes["ChannelID"].InnerText);
                if (Timeline[x].ChannelID == TimelineUpdateID)
                {
                    targetChannel = x;
                    break;
                }
            }
            // If the specific Channel's Timeline has not been created yet, return false
            if (targetChannel < 0)
                return false;
            
            if (Timeline[targetChannel].UpdateFromXML(xmlDocument) == true)
            {
                if (fullmode == true)
                {
                    Timeline[targetChannel].SwapMe = true;
                    while (Timeline[targetChannel].SwapMe == true)
                        Thread.Sleep(0);
                }
                else
                {
                    Timeline[targetChannel].SwapMe = true;
                    SwapSchedule();
                }
                return true;
            }
            else
            {
                return false;
            }

        }

        private int PlayerID = 0;
        private ArrayList Channels = new ArrayList();
        private ArrayList Overrides = new ArrayList();
        /// <summary>
        /// Updates the Current Channel's Configuration creating new display areas, defining the positions,
        /// channels and timelines for those channels. These function is called by the UpdateData() function
        /// but it will only proceed to update the current configuration if the new incoming configuration is
        /// different than the actual configuration.
        /// </summary>
        /// <param name="xmlConfig"></param>
        /// <param name="forceRebuild"></param>
        /// <returns></returns>
        public bool UpdateConfig(XmlDataDocument xmlConfig, bool forceRebuild)
        {
             try
             {       
                    // Check if this update is identical to the present configuration
                    if (CurrentXMLConfig != null)
                    {
                        if (CurrentXMLConfig.InnerXml == xmlConfig.InnerXml)
                        {
                            // if they are identical, and we are not forced to rebuild it, return.
                            // we are forced to rebuild the configuration everytime we lose the d3d device
                            // in that case, even if the configuration is the same, we must continue
                            if (forceRebuild == false)
                                return false;
                        }
                    }

                    // wait till the end of the current render cicle and then
                    // raise a stop signal that will pause the render loop
                    lock (lock_RenderFunction)
                    {
                        if(fullmode) Thread.Sleep(2000);
                        Track.LogInfo("Attempting to apply new configuration...");
                        float MaxWidth = 0;
                        float MaxHeight = 0;
                        int counter = 0;
                        int UpdateToPlayerID;
                        Channels.Clear();

                        // Destroy Previous Displays
                        if (Display != null && Display.Length > 0)
                        {
                            foreach (CDisplay D in Display)
                            {
                                if (D.m_Sprite != null)
                                {
                                    D.m_Sprite.Dispose();
                                    D.m_Surface.Dispose();
                                    D.m_Texture.Dispose();
                                }
                            }
                            PlayerID = -1;
                            Display = null;
                        }

                        // Extract all the info that we need from the xml message
                        CurrentXMLConfig = new XmlDataDocument();
                        CurrentXMLConfig.LoadXml(xmlConfig.InnerXml);
                        XmlNode TextDisplay = xmlConfig.SelectSingleNode("//TEXTDISPLAY");
                        XmlNodeList displayList = xmlConfig.SelectNodes("//TEXTDISPLAYAREA");
                        UpdateToPlayerID = Convert.ToInt32(TextDisplay.Attributes["ID"].InnerText);
                        Display = new CDisplay[displayList.Count];
                        bool CreateOverrideChannel = false;
                        // Create all the display areas                
                        foreach (XmlNode node in displayList)
                        {
                            Display[counter] = new CDisplay();
                            Display[counter].Channel = Convert.ToInt32(node.Attributes["ChannelID"].InnerText);
                            Display[counter].Override = Convert.ToInt32(node.Attributes["OverrideArea"].InnerText);
                            Display[counter].Width = Convert.ToInt32(node.Attributes["Width"].InnerText) * ScaleFactor;
                            Display[counter].Height = Convert.ToInt32(node.Attributes["Height"].InnerText) * ScaleFactor;

                            Display[counter].m_Texture = new Texture(d3d, Convert.ToInt32(Display[counter].Width), Convert.ToInt32(Display[counter].Height), 1, Usage.RenderTarget, Format.X8R8G8B8, Pool.Default);
                            Display[counter].m_Surface = Display[counter].m_Texture.GetSurfaceLevel(0);
                            Display[counter].m_Sprite = new Sprite(d3d);
                            Display[counter].OutPosX = Convert.ToInt32(node.Attributes["OutPosX"].InnerText);
                            Display[counter].OutPosY = Convert.ToInt32(node.Attributes["OutPosY"].InnerText);
                            Display[counter].OffsetX = Convert.ToInt32(node.Attributes["OffsetX"].InnerText) * ScaleFactor;
                            Display[counter].OffsetY = Convert.ToInt32(node.Attributes["OffsetY"].InnerText) * ScaleFactor;

                            if (Display[counter].Width > MaxWidth) MaxWidth = Display[counter].Width;
                            if (Display[counter].Height > MaxHeight) MaxHeight = Display[counter].Height;

                            //Add any new channels
                            if (Channels.Contains(Display[counter].Channel) == false)
                                Channels.Add(Display[counter].Channel);
                            if (Display[counter].Override == 4)
                            {
                                Override = new CDisplay(Display[counter]);
                                CreateOverrideChannel = true;
                            }
                            counter++;
                        }

                        if (CreateOverrideChannel == true)
                        {
                            Channels.Add(4);
                        }

                        SurfaceHelper = new RenderToSurface(d3d, Convert.ToInt32(Display[0].Width), Convert.ToInt32(Display[0].Height), Format.X8R8G8B8, false, DepthFormat.D16);
                        mSprite = new Sprite(d3d);

                        //If forceRebuild == true, meaning that we are recovering the device, save the
                        //Timelines, in case forceRebuild == false, destroy all previous TextTimelines
                        if (forceRebuild != true || Timeline == null)
                        {
                            if (Timeline != null)
                            {
                                for (int z = 0; z < Timeline.Length; z++)
                                    Timeline[z] = null;
                            }
                            Timeline = new CTimeline[Channels.Count];
                            for (int x = 0; x < Channels.Count; x++)
                            {
                                Timeline[x] = new CTimeline(ScaleFactor, AddFontToQueue, MeasureString, MeasureImage);
                                Timeline[x].ChannelID = Convert.ToInt32(Channels[x]);
                            }
                        }


                        ClearDisplay();
                        Track.LogInfo("DONE. a new configuration has been applied (" + Channels.Count.ToString() + " Channels)");
                        PlayerID = UpdateToPlayerID;
                        if (fullmode) Thread.Sleep(2000);
                        return true;
                    }
                }
                catch
                {
                    Track.LogInfo("Error Applying new Configuration (Exception)");
                    PlayerID = -1;
                    return false;
                }

        }
    

        public List<MeasuringFont> DXFontMsr = new List<MeasuringFont>(2);
        private List<DXFont> FontQueue = new List<DXFont>();   

        /// <summary>
        /// Instancializes an object of the class MeasuringFont that contains the requiered information to measure
        /// the lenght of a string when rendered with the specific font and size. This function is called by the
        /// LoadXML() function when fragmenting the TextStrings of each combo.
        /// </summary>
        /// <param name="Aux">The secondary Direct3D device</param>
        /// <param name="face">The desired font face</param>
        /// <param name="size">the desired font size</param>
        public void CreateMeasuringFont(Device Aux, string face, int size, bool bold, bool italics)
        {
            // Create The Measuring Font
            FontWeight isBold = FontWeight.Normal; 
            if(bold == true) isBold = FontWeight.Bold;
                            
            MeasuringFont MsrFont = new MeasuringFont(200);
            Font tempFont = new Font(Aux, size, 0, isBold, 1, italics, CharacterSet.Default, Precision.Default, FontQuality.AntiAliased, PitchAndFamily.FamilyDoNotCare, face);
           
            MsrFont.Description = face.ToString() + "," + bold.ToString() + "," + italics.ToString() + "," + size.ToString();

            // Measure the first 128 characters
            int spacewidth = tempFont.MeasureString(null, "*  *", DrawTextFormat.Left, 0).Width;
            for (int ch = 0; ch < 200; ch++)
                MsrFont.CharWidth[ch] = tempFont.MeasureString(null, "* " + Convert.ToChar(ch).ToString() + " *", DrawTextFormat.Left, 0).Width - spacewidth;
            tempFont.Dispose();
            DXFontMsr.Add(MsrFont);
        }


        /// <summary>
        /// This function return the Descent value of a font. This is the value in pixels between the baseline of a font
        /// and the lowest point of its descent. If the function fails, which is likely possible in font which are not
        /// naturally true type, the function returns a very accurate estimated value.
        /// </summary>
        /// <param name="myFont">The target font</param>
        /// <returns></returns>
        public int GetFontDescent(Font myFont)
        {
            try
            {
                float descent = 0.0f;
                float em = 0.0f;
                
                 System.Drawing.FontFamily family = new System.Drawing.FontFamily(myFont.Description.FaceName);
                int Size = myFont.Description.Height;
                int Style = 0;
                if (myFont.Description.FaceName.ToUpper().Contains("BOLD") || myFont.Description.Weight == FontWeight.Bold)
                    Style += 1;
                if (myFont.Description.FaceName.ToUpper().Contains("ITALIC") || myFont.Description.IsItalic == true)
                    Style += 2;

                if (Style == 0)
                {
                    descent = family.GetCellDescent(System.Drawing.FontStyle.Regular);
                    em = family.GetEmHeight(System.Drawing.FontStyle.Regular);

                }
                else if (Style == 1)
                {
                    descent = family.GetCellDescent(System.Drawing.FontStyle.Bold);
                    em = family.GetEmHeight(System.Drawing.FontStyle.Bold);
                }
                else if (Style == 2)
                {
                    descent = family.GetCellDescent(System.Drawing.FontStyle.Italic);
                    em = family.GetEmHeight(System.Drawing.FontStyle.Italic);
                }
                else if (Style == 3)
                {
                    descent = family.GetCellDescent(System.Drawing.FontStyle.Bold | System.Drawing.FontStyle.Italic);
                    em = family.GetEmHeight(System.Drawing.FontStyle.Bold | System.Drawing.FontStyle.Italic);
                }

                if (descent != 0.0f && em != 0.0f)
                {
                    Track.LogInfo("Extracting descent value for " + myFont.Description.FaceName);
                    return Convert.ToInt32((descent / em) * Size);
                }
                else
                {
                    Track.LogInfo("Unable to calculate descent value for " + myFont.Description.FaceName + " with style " + myFont.Description.Weight);
                    return myFont.Description.Height / 5;
                }
            }
            catch
            {
                Track.LogInfo("Error calculating descent value for " + myFont.Description.FaceName + " with style " + myFont.Description.Weight);
                return myFont.Description.Height / 5;
            }
        }
        /// <summary>
        /// Adds the information of a requiered font to the DXFontList and creates an object of the class Measuringfont
        /// with the desired font face and size. The DXFontList acts as a queue list for fonts that need to be created 
        /// from where only one font will be created per rendered frame by the function CreateNextFont().
        /// </summary>
        /// <param name="face">The desired font face</param>
        /// <param name="size">The desired font size</param>
        /// <returns></returns>
        public int AddFontToQueue(string face, int size)
        {
            face = face.ToUpper();
            if (face == null) return 0;
            bool bold = false;
            bool italics = false;
            if(face.ToUpper().Contains(",BOLD")) 
                bold = true;
            if(face.ToUpper().Contains(",ITALICS")) 
                italics = true;
            if(face.Contains(","))
                face = Expression.GetStringValue(face);
            if (face == "") face = "ARIAL";

            for (int i = 0; i < FontQueue.Count; i++)
            {
                if (FontQueue[i].Face == face && FontQueue[i].Size == size
                    && FontQueue[i].Bold == bold && FontQueue[i].Italics == italics)
                {
                    return i;
                }
            }

            // If the Font does not exist, we create an entry for it so the function
            // CreateNextFont can create it the next time its called
            FontQueue.Add(new DXFont(face, size, bold, italics));
            CreateMeasuringFont(AuxDevice, face, size, bold, italics);
            
            return (FontQueue.Count - 1);
        }


        /// <summary>
        /// This function determines if a given font is installed in the system. This function is mainly used
        /// by the application while running on preview mode
        /// </summary>
        /// <param name="name">the face name of the font</param>
        /// <returns></returns>
        private bool isFontInstalled(string name)
        {
            System.Drawing.FontFamily [] families = new System.Drawing.Text.InstalledFontCollection().Families;
            for (int x = 0; x < families.Length; x++)
            {
                if (families[x].Name == name)
                    return true;
            }
            return false;
        }


   
        /// <summary>
        /// Creates the next requiered font on the DXFontList queue list. This function is called at the begining
        /// of the Render() funtion ensuring that only one font will be created each frame limiting the chances of 
        /// delaying the rendering process.
        /// </summary>
        private void LoadNextFont()
        {
            if (fullmode != true)
            {
                if (isFontInstalled(FontQueue[DXFontLib.Count].Face) != true)
                    NotifyMissingMedia = 300;
            }

            if (FontQueue[DXFontLib.Count].Bold == true)
                DXFontLib.Add(new Font(d3d, FontQueue[DXFontLib.Count].Size, 0, FontWeight.Bold, 1,
                                     FontQueue[DXFontLib.Count].Italics, CharacterSet.Default, Precision.Default,
                                     FontQuality.ClearTypeNatural, PitchAndFamily.DefaultPitch,
                                     FontQueue[DXFontLib.Count].Face));
            else
                DXFontLib.Add(new Font(d3d, FontQueue[DXFontLib.Count].Size, 0, FontWeight.Normal, 1,
                                     FontQueue[DXFontLib.Count].Italics, CharacterSet.Default, Precision.Default,
                                     FontQuality.ClearTypeNatural, PitchAndFamily.DefaultPitch,
                                     FontQueue[DXFontLib.Count].Face));
            DXFontLib[DXFontLib.Count - 1].PreloadCharacters(0, 200);
            Track.LogInfo("New Font Created " + DXFontLib[DXFontLib.Count - 1].Description.FaceName + " " + DXFontLib[DXFontLib.Count - 1].Description.Height.ToString() + " " + DXFontLib[DXFontLib.Count - 1].Description.Weight);
            DXFontDesc.Add(GetFontDescent(DXFontLib[DXFontLib.Count - 1]));
            MemoryReport();            
        }

        private int lastFont = 0;
        /// <summary>
        /// This function effectively releases the memory allocated by the usage of a font by disposing the font
        /// and immediatly recreating a new font object using the same description of the disposed font.
        /// </summary>
        private void ReloadFont()
        {    
            FontDescription Description = DXFontLib[lastFont].Description;
            Track.LogInfo("Reloading Font: " + Description.FaceName);
            DXFontLib[lastFont].Dispose();
            DXFontLib[lastFont] = new Font(d3d, Description);
            lastFont++;
            if (lastFont >= DXFontLib.Count)          
                lastFont = 0;
            CicleCount = 0;
            MemoryReport();
        }

        /// <summary>
        /// This function writes a report of the memory usage to the log file.
        /// the function reports Available Ram, PageFile usage and Video memory available
        /// </summary>
        private void MemoryReport()
        {
            Track.LogInfo("-> Memory Usage: (" + Monitor.GetRamUsage().ToString() + "mb Ram available) / (" + Monitor.GetPfUsage().ToString() + "mb PageFile Memory used) / (" + GetVideoMemory().ToString() + "mb Video Memory)");
        }
        private int GetVideoMemory()
        {
            return (d3d.AvailableTextureMemory / 1024) / 1024;
        }
        /// <summary>
        /// Measures the size of a string when displayed on the screen using an specific face and size.
        /// This function is called by the ArrangeSubstrings function.
        /// </summary>
        /// <param name="mystring">The string to measure</param>
        /// <param name="face">The desired font face</param>
        /// <param name="size">The desired font size</param>
        /// <returns></returns>
        public int MeasureString(string mystring, string face, int size)
        {
            if (face == "" || face == null) face = "ARIAL";
            try
            {
                string description = Expression.GetStringValue("\"" + face + "\"", ",", 0).ToString() + "," +
                                     face.ToUpper().Contains(",BOLD").ToString() + "," + face.ToUpper().Contains(",ITALICS").ToString()
                                     + "," + size.ToString();
                for (int x = 0; x < DXFontMsr.Count; x++)
                {
                    if (description == DXFontMsr[x].Description)
                    {
                        int totalWidth = 0;
                        foreach (char ch in mystring.ToCharArray())
                        {
                            if (Convert.ToInt32(ch) < 200)
                                totalWidth += DXFontMsr[x].CharWidth[Convert.ToInt32(ch)];
                        }
                        return totalWidth;
                    }
                }
                return 0;
            }
            catch 
            {
                return 0;
            }
        }


        public List<int> ImageQueue = new List<int>();
        private List<DXImage> DXImageLib = new List<DXImage>();
        /// <summary>
        /// This function copies a set of images from a network-accessible volume to a local folder using the 
        /// information contained in a ImageTag xml stream. After the images are copied to a local folder they are
        /// loaded into the memory one each frame. This function is called by the UpdateData() function. 
        /// </summary>
        /// <param name="xmlStream">String containing the ImageTag xml document</param>
        public void LoadImagesToQueue(string xmlStream)
        {
            int x = -1;
            try
            {
                if (ImageQueue.Count > 10)
                {
                    Track.LogInfo("WARNING: There are more than 10 Images in Queue to load - New updates will be ignored");
                    return;
                }

                // Load The XML Information
                XmlDataDocument XMLDocument_Images = new XmlDataDocument();
                if(xmlStream.StartsWith("<"))
                    XMLDocument_Images.LoadXml(xmlStream);
                else
                    XMLDocument_Images.Load(xmlStream);
                XmlNodeList ImageList = XMLDocument_Images.SelectNodes("//IMAGETAG");

                string FilePath, Destination;

                foreach (XmlNode item in ImageList)
                {
                    x = ExistInImageLib(Convert.ToInt32(item.Attributes["ID"].InnerText));
                    FilePath = item.Attributes["ImagePath"].InnerText;
                    Destination = String.Format("C:\\TextOverlay Images\\{0}.image", item.Attributes["ID"].InnerText);

                    // First, if X < 0 the image has not been created into the library
                    // Proceed to create it in the library;
                    if (x < 0)
                    {
                        Track.LogInfo("Preloading Image: " + FilePath);
                        DXImageLib.Add(new DXImage());
                        DXImageLib[DXImageLib.Count - 1].ID = Convert.ToInt32(item.Attributes["ID"].InnerText);
                        DXImageLib[DXImageLib.Count - 1].Path = Destination;
                        DXImageLib[DXImageLib.Count - 1].Width = GetWidthFromFilename(FilePath);
                        DXImageLib[DXImageLib.Count - 1].Update = item.Attributes["LastUpdated"].InnerText;
                        if(File.Exists(FilePath))
                        {
                            File.Copy(FilePath, Destination, true);
                            Track.LogInfo("Preloading Succesful at Position " + ImageQueue.Count.ToString());
                            ImageQueue.Add(DXImageLib.Count - 1);
                        }
                        else
                        {
                            DXImageLib[DXImageLib.Count - 1].Path = "";
                            Track.LogInfo("Preloading Failed (Invalid Path)");
                            ReportToSCS("<Activity ID=\"20021\" Desc=\"Invalid Image Path\" />");
                        }
                        
                    }

                    if (x >= 0)
                    {
                        if (DXImageLib[x].Path == "")
                        {
                            if (File.Exists(FilePath))
                            {
                                Track.LogInfo("Reattempting Preloading Image: " + FilePath);
                                File.Copy(FilePath, Destination, true);
                                DXImageLib[x].Path = Destination;
                                ImageQueue.Add(x);
                            }
                            else
                            {
                                DXImageLib[x].Path = "";
                            }
                        }
                        else if (DXImageLib[x].Update != item.Attributes["LastUpdated"].InnerText)
                        {
                            Track.LogInfo("Preloading new version of Image: " + FilePath);
                            DXImageLib.Add(new DXImage());
                            DXImageLib[DXImageLib.Count - 1].ID = Convert.ToInt32(item.Attributes["ID"].InnerText);
                            DXImageLib[DXImageLib.Count - 1].Path = Destination;
                            DXImageLib[DXImageLib.Count - 1].Width = GetWidthFromFilename(FilePath);
                            DXImageLib[DXImageLib.Count - 1].Update = item.Attributes["LastUpdated"].InnerText;
                            if (File.Exists(FilePath))
                            {
                                File.Copy(FilePath, Destination, true);
                                Track.LogInfo("Preloading Succesful at Position " + ImageQueue.Count.ToString());
                                ImageQueue.Add(DXImageLib.Count - 2);
                            }
                            else
                            {
                                DXImageLib[DXImageLib.Count - 1].Path = "";
                                Track.LogInfo("Preloading Failed (Invalid Path " + FilePath + ")");
                            }
                            DXImageLib.RemoveAt(x);
                        }
                    }    
                }
            }
            catch 
            {
                Track.LogInfo("Error Processing XML Image Update");
            }
            
        }

        /// <summary>
        /// This function looks into the ImageQueue queue for any image file awaiting to be loaded into the memory
        /// this process is performed maximum once per frame.
        /// </summary>
        public void LoadNextImage()
        {
            int Index = -1;
            try
            {
                if (ImageQueue.Count > 0)
                {
                    Index = ImageQueue[0];
                    try
                    {
                        if (DXImageLib[Index].Path != "")
                        {
                            Track.LogInfo("Attempting to Load Image " + DXImageLib[Index].Path + " into memory (" + GetVideoMemory().ToString() + "mb of video memory available)");
                            ImageInformation Image = TextureLoader.ImageInformationFromFile(DXImageLib[Index].Path);
                            DXImageLib[Index].Height = Image.Height * ScaleFactor;
                            DXImageLib[Index].Image = TextureLoader.FromFile(d3d, DXImageLib[Index].Path, Image.Width, Image.Height, 1, Usage.None, Format.Unknown, Pool.Default, Filter.None, Filter.None, 0);
                            DXImageLib[Index].XSprite = new Sprite(d3d);
                            ImageQueue.RemoveAt(0);
                        }
                    }
                    catch (Direct3DXException ex)
                    {
                        DXImageLib[Index].Path = "Bad Image";
                        DXImageLib[Index].Image = null;
                        ImageQueue.RemoveAt(0);
                        Track.LogInfo("Error Loading Image: " + ex.Message);
                    }
                }
            }
            catch (Exception ex)
            {
                Track.LogInfo("Error Loading Image " + Index.ToString());
                ImageQueue.RemoveAt(0);
                Track.LogInfo(ex.Message);
            }
        }

        /// <summary>
        /// This function determines if an image exist on the ImageLib using its ID. This function is called by the
        /// LoadImagesToQueue function
        /// </summary>
        /// <param name="Ident">the Image ID</param>
        /// <returns>The index of the Image in the DXImageLib or -1 if the image does not exist</returns>
        private int ExistInImageLib(int Ident)
        {
            for (int x = 0; x < DXImageLib.Count; x++)
            {
                if (DXImageLib[x].ID == Ident)
                    return x;
            }
            return -1;
        }

        /// <summary>
        /// This function retreives the Image width from its filename.
        /// </summary>
        /// <param name="filename">The filename in string format</param>
        /// <returns>the width of the image</returns>
        private int GetWidthFromFilename(string filename)
        {
            // works for widths of three numerals
            try
            {
                int start = 0;
                int end = filename.IndexOf(".");
                for (int x = 0; x < end; x++)
                {
                    if (filename[end - x].ToString() == "_")
                    {
                        start = (end - x) + 1;
                        break;
                    }
                }
                int width = Convert.ToInt32(filename.Substring(start, end - start));
                return width;
            }
            catch
            {
                return 0;
            }
        }
        
        /// <summary>
        /// This function returns the width of an image. This function is called by the ArrangeSubstrings function
        /// when calculating the requiered spaced inside a text string to display a specific image.
        /// </summary>
        /// <param name="index">Index to the specific image in the DXImageLib</param>
        /// <returns></returns>
        public int MeasureImage(int index)
        {
            int x = LocateImage(index);
            if (x > -1)
            {
                return Convert.ToInt32(DXImageLib[x].Width * ScaleFactor);   
            }
            else
            {
                Track.LogInfo("Could not measure image id " + index.ToString());
                return 0;
            }
        }




    }
}
